#region Copyright (c) 2003, newtelligence AG. All rights reserved.

/*
// Copyright (c) 2003, newtelligence AG. (http://www.newtelligence.com)
// Original BlogX Source Code: Copyright (c) 2003, Chris Anderson (http://simplegeek.com)
// All rights reserved.
//  
// Redistribution and use in source and binary forms, with or without modification, are permitted 
// provided that the following conditions are met: 
//  
// (1) Redistributions of source code must retain the above copyright notice, this list of 
// conditions and the following disclaimer. 
// (2) Redistributions in binary form must reproduce the above copyright notice, this list of 
// conditions and the following disclaimer in the documentation and/or other materials 
// provided with the distribution. 
// (3) Neither the name of the newtelligence AG nor the names of its contributors may be used 
// to endorse or promote products derived from this software without specific prior 
// written permission.
//      
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS 
// OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY 
// AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER 
// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 
// OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// -------------------------------------------------------------------------
//
// Original BlogX source code (c) 2003 by Chris Anderson (http://simplegeek.com)
// 
// newtelligence is a registered trademark of newtelligence Aktiengesellschaft.
// 
// For portions of this software, the some additional copyright notices may apply 
// which can either be found in the license.txt file included in the source distribution
// or following this notice. 
//
*/

#endregion

using System;
using System.Collections;
using System.IO;
using System.Security.Cryptography;
using System.Security.Principal;
using System.Text;
using System.Threading;
using System.Web;
using System.Xml.Serialization;
using newtelligence.DasBlog.Runtime;
using newtelligence.DasBlog.Web.Core.Config;

namespace newtelligence.DasBlog.Web.Core
{
    public class SiteSecurity
    {
        public static bool IsInRole(string role)
        {
            return HttpContext.Current.User.IsInRole(role);
        }

        /// <summary>
        /// Redirects to the a file if the user is not in role administrator.
        /// </summary>
        /// <param name="httpResponse">The response which can be redirected.</param>
        public static void IfIsNotAdminRedirect(HttpResponse httpResponse)
        {
            if (!IsInRole(CoreSecurityNames.Roles.Admin) && httpResponse != null)
            {
                httpResponse.Redirect(CoreFilePaths.FormatPage(CoreFileNames.AccessDenied));
            }
        }

        public static void SetSecurity(SiteSecurityConfig ssc)
        {
            XmlSerializer ser = new XmlSerializer(typeof (SiteSecurityConfig));
            using (StreamWriter writer = new StreamWriter(SiteConfig.GetSecurityFilePathFromCurrentContext()))
            {
                ser.Serialize(writer, ssc);
            }
        }

        /// <summary>
        /// Shortcut for determining a valid contributor to the blog...currently only users in the "admin" role
        /// or the "contributor" role are supported.
        /// </summary>
        public static bool IsValidContributor()
        {
            return (IsInRole("admin") || IsInRole("contributor"));
        }

        public static SiteSecurityConfig GetSecurity()
        {
            string fullPath = SiteConfig.GetSecurityFilePathFromCurrentContext();

            if (File.Exists(fullPath))
            {
                return GetSecurity(fullPath);
            }

            return new SiteSecurityConfig();
        }

        // make it easier to get the sitesecurity from a non-web app.
        public static SiteSecurityConfig GetSecurity(string path)
        {
            // argument validation
            if (path == null || path.Length == 0)
            {
                throw new ArgumentNullException("path");
            }

            // setup the serializer
            XmlSerializer ser = new XmlSerializer(typeof (SiteSecurityConfig));

            // load the security config
            using (StreamReader reader = new StreamReader(path))
            {
                return (SiteSecurityConfig) ser.Deserialize(reader);
            }
        }

        public static void AddUser(string userName, string password, string role, bool ask, string emailAddress)
        {
            SiteSecurityConfig ssc = GetSecurity();
            User user = new User();
            user.Name = userName;
            user.Password = password;
            user.Role = role;
            user.Ask = ask;
            user.EmailAddress = emailAddress;
            ssc.Users.Add(user);

            SetSecurity(ssc);
        }

        /// <summary>
        /// This function takes a password and the userName to
        /// compare the password with the password asigned to the userName.
        /// Both passwords, only one or none will exist as md5 hashed.  
        /// </summary>
        /// <param name="userName"></param>
        /// <param name="password"></param>
        /// <returns>user as UserToken.</returns>
        public static UserToken Login(string userName, string password)
        {
            UserToken token = null;
            SiteSecurityConfig ssc = GetSecurity();
            foreach (User user in ssc.Users)
            {
                if (user.Name.ToUpper() == userName.ToUpper() && user.Active)
                {
                    if ((IsCleanStringEncrypted(user.Password) && IsCleanStringEncrypted(password)) ||
                        (!IsCleanStringEncrypted(user.Password) && !IsCleanStringEncrypted(password)))
                    {
                        if (user.Password == password)
                        {
                            token = user.ToToken();
                            break;
                        }
                        else if (user.Password == Encrypt(password))
                        {
                            token = user.ToToken();
                            break;
                        }
                    }
                    else if ((IsCleanStringEncrypted(user.Password) && !IsCleanStringEncrypted(password)))
                    {
                        if (user.Password == Encrypt(password))
                        {
                            token = user.ToToken();
                            break;
                        }
                    }
                    else
                    {
                        if (Encrypt(user.Password) == password)
                        {
                            token = user.ToToken();
                            break;
                        }
                    }
                }
            }

            if (token == null)
            {
                LogFailure(userName);
            }
            else
            {
                LogSuccess(token.Name);
                GenericIdentity identity = new GenericIdentity(token.Name, "Custom");
                GenericPrincipal principal = new GenericPrincipal(identity, new string[] {token.Role});
                HttpContext.Current.User = principal;
                Thread.CurrentPrincipal = principal;
            }

            return token;
        }

        private static void LogFailure(string userName)
        {
            ILoggingDataService loggingService =
                LoggingDataServiceFactory.GetService(SiteConfig.GetLogPathFromCurrentContext());
            if (HttpContext.Current == null)
            {
                loggingService.AddEvent(new EventDataItem(EventCodes.SecurityFailure, userName, "non-web"));
            }
            else
            {
                loggingService.AddEvent(
                    new EventDataItem(EventCodes.SecurityFailure, userName, HttpContext.Current.Request.UserHostAddress));
            }
        }

        private static void LogSuccess(string userName)
        {
            ILoggingDataService loggingService =
                LoggingDataServiceFactory.GetService(SiteConfig.GetLogPathFromCurrentContext());
            if (HttpContext.Current == null)
            {
                loggingService.AddEvent(new EventDataItem(EventCodes.SecuritySuccess, userName, "non-web"));
            }
            else
            {
                loggingService.AddEvent(
                    new EventDataItem(EventCodes.SecuritySuccess, userName, HttpContext.Current.Request.UserHostAddress));
            }
        }

        /// <summary>
        /// This function takes a password, the  challenge and the userName to
        /// make an super challenge like on the client side. 
        /// </summary>
        /// <param name="userName"></param>
        /// <param name="clientHash"></param>
        /// <param name="challenge"></param>
        /// <returns>user as UserToken.</returns>
        public static UserToken Login(string userName, string clientHash, string challenge)
        {
            UserToken token = null;
            SiteSecurityConfig ssc = GetSecurity();

            foreach (User user in ssc.Users)
            {
                if (user.Name.ToUpper() == userName.ToUpper() && user.Active)
                {
                    if (DoSuperChallenge(challenge, user.Password, userName, clientHash))
                    {
                        token = user.ToToken();
                        break;
                    }
                }
            }

            if (token == null)
            {
                LogFailure(userName);
            }
            else
            {
                LogSuccess(token.Name);
                GenericIdentity identity = new GenericIdentity(token.Name, "Custom");
                GenericPrincipal principal = new GenericPrincipal(identity, new string[] {token.Role});
                HttpContext.Current.User = principal;
                Thread.CurrentPrincipal = principal;
            }

            return token;
        }

        /// <summary>
        /// This function builds from the challange, the (md5 hashed) password and the userName
        /// an md5 hashed string and compare them with the cryptPassword from the client. 
        /// </summary>
        /// <param name="challenge"></param>
        /// <param name="passwordIN"></param>
        /// <param name="userName"></param>
        /// <param name="clientHash"></param>
        /// <returns>true if coparing was good otherwise false</returns>
        public static bool DoSuperChallenge(string challenge, string passwordIN, string userName, string clientHash)
        {
            string password = Encrypt(passwordIN);
            string serverHash = Encrypt(challenge + password + userName);

            if (clientHash == serverHash)
            {
                return true;
            }
            else
            {
                return false;
            }
        }

        /// <summary>
        /// This function compute a given string with the md5 hash algorithm. 
        /// </summary>
        /// <param name="cleanString"></param>
        /// <returns>hash from cleanString as string.</returns>
        public static string Encrypt(string cleanString)
        {
            if (!IsCleanStringEncrypted(cleanString))
            {
                Byte[] clearBytes = new UnicodeEncoding().GetBytes(cleanString);
                Byte[] hashedBytes = ((HashAlgorithm) CryptoConfig.CreateFromName("MD5")).ComputeHash(clearBytes);

                return BitConverter.ToString(hashedBytes);
            }
            else
            {
                return cleanString;
            }
        }

        /// <summary>
        /// This function checks an given string if it is always encrypted 
        /// </summary>
        /// <param name="cleanString">clean string to check</param>
        /// <returns>true or false depending on string type.</returns>
        public static bool IsCleanStringEncrypted(string cleanString)
        {
            if (cleanString.Length == 47 && cleanString.Replace("-", "").Length == 32)
            {
                return true;
            }
            else
            {
                return false;
            }
        }

        /// <summary>
        /// This function takes a password and then
        /// writes the siteSecurity.config file.
        /// </summary>
        /// <param name="userName">The username of the logged in user</param>
        /// <param name="password">The plain text password of the logged in user</param>
        public static void SetPassword(string userName, string password)
        {
            SiteSecurityConfig ssc = GetSecurity();

            foreach (User user in ssc.Users)
            {
                if (user.Name.ToUpper() == userName.ToUpper())
                {
                    user.Password = password;
                }
            }

            // write out the changes
            SetSecurity(ssc);
        }

        public static UserToken GetToken(string userName)
        {
            if (userName != null)
            {
                SiteSecurityConfig ssc = GetSecurity();

                foreach (User user in ssc.Users)
                {
                    if (user.Name.ToUpper() == userName.ToUpper())
                    {
                        return user.ToToken();
                    }
                }
            }

            return null;
        }

        /// <summary>
        /// This function looks up a given username and returns the associated
        /// User object.
        /// </summary>
        /// <param name="userName">The username to look up.</param>
        /// <returns>The User object corresponding to the provided username.</returns>
        public static User GetUser(string userName)
        {
            if (userName != null)
            {
                SiteSecurityConfig ssc = GetSecurity();

                foreach (User user in ssc.Users)
                {
                    if (user.Name.ToUpper() == userName.ToUpper())
                    {
                        return user;
                    }
                }
            }

            return null;
        }

        /// <summary>
        /// This function looks up a given username and returns the associated
        /// User object.
        /// </summary>
        /// <param name="email">The email to look up.</param>
        /// <returns>The User object corresponding to the provided username.</returns>
        public static User GetUserByEmail(string email)
        {
            if (email == null)
            {
                return null;
            }

            SiteSecurityConfig ssc = GetSecurity();

            foreach (User user in ssc.Users)
            {
                if (user.EmailAddress != null)
                {
                    if (user.EmailAddress.ToUpper() == email.ToUpper())
                    {
                        return user;
                    }
                }
            }

            return null;
        }

        public static User GetUserByDisplayName(string displayName)
        {
            SiteSecurityConfig ssc = GetSecurity();
            displayName = displayName.ToUpper();

            foreach (User user in ssc.Users)
            {
                if (user.DisplayName != null && user.DisplayName.ToUpper() == displayName)
                {
                    return user;
                }
            }

            return null;
        }
    }

    public class SiteSecurityConfig
    {
        private readonly UserCollection users = new UserCollection();

        public UserCollection Users
        {
            get { return users; }
        }
    }

    public class UserToken
    {
        private string name;
        private string role;

        public string Name
        {
            get { return name; }
            set { name = value; }
        }

        public string Role
        {
            get { return role; }
            set { role = value; }
        }
    }

    public class User
    {
        private bool active = true;
        private bool ask;
        private string displayName;
        private string emailAddress;
        private string name;

        private bool notifyOnAllComment = false;
        private bool notifyOnNewPost = false;
        private bool notifyOnOwnComment = false;
        private string password;
        private string role;

        public string Name
        {
            get { return name; }
            set { name = value; }
        }

        [XmlIgnore]
        public string Password
        {
            get { return password; }
            set { password = SiteSecurity.Encrypt(value); }
        }

        public string Role
        {
            get { return role; }
            set { role = value; }
        }

        public bool Ask
        {
            get { return ask; }
            set { ask = value; }
        }

        public string EmailAddress
        {
            get { return emailAddress; }
            set { emailAddress = value; }
        }

        public string DisplayName
        {
            get { return displayName; }
            set { displayName = value; }
        }

        public bool NotifyOnNewPost
        {
            get { return notifyOnNewPost; }
            set { notifyOnNewPost = value; }
        }

        public bool NotifyOnAllComment
        {
            get { return notifyOnAllComment; }
            set { notifyOnAllComment = value; }
        }

        public bool NotifyOnOwnComment
        {
            get { return notifyOnOwnComment; }
            set { notifyOnOwnComment = value; }
        }

        public bool Active
        {
            get { return active; }
            set { active = value; }
        }

        /// <summary>
        /// Encrypted password for serialization.
        /// </summary>
        [XmlElement("Password")]
        public string XmlPassword
        {
            get { return Password; }
            set { password = value; }
        }

        public UserToken ToToken()
        {
            UserToken token = new UserToken();
            token.Name = name;
            token.Role = role;
            return token;
        }
    }

    /// <summary>
    /// A collection of elements of type User
    /// </summary>
    public class UserCollection : CollectionBase
    {
        /// <summary>
        /// Initializes a new empty instance of the UserCollection class.
        /// </summary>
        public UserCollection()
        {
        }

        /// <summary>
        /// Initializes a new instance of the UserCollection class, containing elements
        /// copied from an array.
        /// </summary>
        /// <param name="items">
        /// The array whose elements are to be added to the new UserCollection.
        /// </param>
        public UserCollection(User[] items)
        {
            AddRange(items);
        }

        /// <summary>
        /// Initializes a new instance of the UserCollection class, containing elements
        /// copied from another instance of UserCollection
        /// </summary>
        /// <param name="items">
        /// The UserCollection whose elements are to be added to the new UserCollection.
        /// </param>
        public UserCollection(UserCollection items)
        {
            AddRange(items);
        }

        /// <summary>
        /// Gets or sets the User at the given index in this UserCollection.
        /// </summary>
        public virtual User this[int index]
        {
            get { return (User) List[index]; }
            set { List[index] = value; }
        }

        /// <summary>
        /// Adds the elements of an array to the end of this UserCollection.
        /// </summary>
        /// <param name="items">
        /// The array whose elements are to be added to the end of this UserCollection.
        /// </param>
        public virtual void AddRange(User[] items)
        {
            foreach (User item in items)
            {
                List.Add(item);
            }
        }

        /// <summary>
        /// Adds the elements of another UserCollection to the end of this UserCollection.
        /// </summary>
        /// <param name="items">
        /// The UserCollection whose elements are to be added to the end of this UserCollection.
        /// </param>
        public virtual void AddRange(UserCollection items)
        {
            foreach (User item in items)
            {
                List.Add(item);
            }
        }

        /// <summary>
        /// Adds an instance of type User to the end of this UserCollection.
        /// </summary>
        /// <param name="value">
        /// The User to be added to the end of this UserCollection.
        /// </param>
        public virtual void Add(User value)
        {
            List.Add(value);
        }

        /// <summary>
        /// Determines whether a specfic User value is in this UserCollection.
        /// </summary>
        /// <param name="value">
        /// The User value to locate in this UserCollection.
        /// </param>
        /// <returns>
        /// true if value is found in this UserCollection;
        /// false otherwise.
        /// </returns>
        public virtual bool Contains(User value)
        {
            return List.Contains(value);
        }

        /// <summary>
        /// Return the zero-based index of the first occurrence of a specific value
        /// in this UserCollection
        /// </summary>
        /// <param name="value">
        /// The User value to locate in the UserCollection.
        /// </param>
        /// <returns>
        /// The zero-based index of the first occurrence of the _ELEMENT value if found;
        /// -1 otherwise.
        /// </returns>
        public virtual int IndexOf(User value)
        {
            return List.IndexOf(value);
        }

        /// <summary>
        /// Inserts an element into the UserCollection at the specified index
        /// </summary>
        /// <param name="index">
        /// The index at which the User is to be inserted.
        /// </param>
        /// <param name="value">
        /// The User to insert.
        /// </param>
        public virtual void Insert(int index, User value)
        {
            List.Insert(index, value);
        }

        /// <summary>
        /// Removes the first occurrence of a specific User from this UserCollection.
        /// </summary>
        /// <param name="value">
        /// The User value to remove from this UserCollection.
        /// </param>
        public virtual void Remove(User value)
        {
            List.Remove(value);
        }

        /// <summary>
        /// Returns an enumerator that can iterate through the elements of this UserCollection.
        /// </summary>
        /// <returns>
        /// An object that implements System.Collections.IEnumerator.
        /// </returns>        
        public new virtual Enumerator GetEnumerator()
        {
            return new Enumerator(this);
        }

        public void Sort(IComparer comparer)
        {
            InnerList.Sort(comparer);
        }

        #region Nested type: Enumerator

        /// <summary>
        /// Type-specific enumeration class, used by UserCollection.GetEnumerator.
        /// </summary>
        public class Enumerator : IEnumerator
        {
            private readonly IEnumerator wrapped;

            public Enumerator(IEnumerable collection)
            {
                wrapped = collection.GetEnumerator();
            }

            public User Current
            {
                get { return (User) (wrapped.Current); }
            }

            #region IEnumerator Members

            object IEnumerator.Current
            {
                get { return wrapped.Current; }
            }

            public bool MoveNext()
            {
                return wrapped.MoveNext();
            }

            public void Reset()
            {
                wrapped.Reset();
            }

            #endregion
        }

        #endregion
    }
}